package algorytmy.numeryczne;
import java.util.ArrayList;
import java.util.Iterator;
import struktury.StackList;
/**
 * Obiekt <code>Funkcja<code> reprezentuje funkcję
 * o podanym przez użytkownika wzorze.
 * Posiada metodę obliczajacą jej wartosc w danym punkcie.
 * @author Piotrek
 *
 */
public class Funkcja {
	/**
	 * lista operatorów jedno argumentowych
	 */
	private static ArrayList<String> lZn;
	/**
	 * Wzór funkcji zapisany w ciagu string
	 */
	private String funkcja;

	public Funkcja(String f) {
		funkcja = f;
		lZn = new ArrayList<String>();
		lZn.add("sin");
		lZn.add("cos");
		lZn.add("tg");
		lZn.add("ctg");
		lZn.add("log");
	}

	public static void main(String[] ars) {
		Funkcja f1 = new Funkcja("-1-0.6*x+0.25*x^2");
		System.out.println(f1.wartoscFunkcji(4));
	}
	/**
	 * Dodaje obsluge nowego operatora jednoargumentowego
	 * @param zn ciag string okreslajacy 
	 * jaiki nowy operator jednoargumentowy ma obslugiwac  
	 *narzedzie liczace wartosc funkcji w punkcie
	 */
	public static void operatorJenoArgumentowy(String zn) {
		lZn.add(zn);
	}
	/**
	 * Rozklada wprowadzony ciag string na 
	 * skladowe np. nawiasy, liczby, operatry
	 * @param s wyrazene artmetyczne w postaci ciagu String
	 * @return tablica stringow zawierajaca skladowe równania
	 */
	protected static ArrayList<String> getArrayString(String s) {
		ArrayList<String> znaki = new ArrayList<String>();
		while (s.length() > 0) {
			String pom = s.substring(0, 1);
			if (pom.matches("-|\\d|x|e|π")) {
				StringBuilder bob = new StringBuilder(pom);
				if (pom.matches("-")) {
					if (znaki.isEmpty()
							|| "(".equals(znaki.get(znaki.size() - 1))) {
						s = s.substring(1);
						if (s.length() > 0)
							pom = s.substring(0, 1);
						if (pom.matches("\\d")) {
							while (pom.matches("[0-9\\.]") && s.length() > 0) {
								bob.append(pom);
								s = s.substring(1);
								if (s.length() > 0) {
									pom = s.substring(0, 1);
								}
							}
						} else {
							bob.append(pom);
							s = s.substring(1);
						}
						znaki.add(bob.toString());

					} else {
						znaki.add(pom);
						s = s.substring(1);
					}
				} else {
					if (pom.matches("\\d")) {
						s = s.substring(1);
						if (s.length() > 0)
							pom = s.substring(0, 1);
						while (pom.matches("[0-9\\.]") && s.length() > 0) {
							bob.append(pom);
							s = s.substring(1);
							if (s.length() > 0) {
								pom = s.substring(0, 1);
							}
						}
						znaki.add(bob.toString());
					} else {
						znaki.add(pom);
						s = s.substring(1);
					}
				}
			} else {
				if (pom.matches("\\p{Alpha}") && pom.matches("[^x]")) {
					StringBuilder bob = new StringBuilder(pom);
					s = s.substring(1);
					if (s.length() > 0) {
						pom = s.substring(0, 1);
					}
					while (pom.matches("\\p{Alpha}") && s.length() > 0) {

						bob.append(pom);
						s = s.substring(1);
						if (s.length() > 0) {
							pom = s.substring(0, 1);
						}
					}
					if (lZn.contains(bob.toString()))
						znaki.add(bob.toString());
					else
						znaki.add(null);
				} else {
					znaki.add(pom);
					if (s.length() > 0)
						s = s.substring(1);
				}
			}
		}
		return znaki;
	}
	/**
	 * Zamienia standarwoy zapis wyrazenia na odwortna 
	 * notacje polska
	 * @param tab skladowe wyrazenia 
	 * @return tablica stringow zawierajaca skladowe 
	 * wyrazenia(liczby i operatory) zapisane w odwrotnej 
	 * natacji polskiej.
	 * @throws NawiasyEx Wyjatego pojawia sie gdy w wyrazeniu
	 * wystepuje nieprawidlowa ilosc nawiasow 
	 */
	protected static ArrayList<String> transelt(ArrayList<String> tab)
			throws NawiasyEx {
		ArrayList<String> result = new ArrayList<String>();
		StackList<String> temp = new StackList<String>();
		Iterator<String> it = tab.iterator();
		while (it.hasNext()) {
			String pom = it.next();
			if (pom.matches("(-*\\d+(\\.?\\d+)*)|-?x|-?e|-?π")) {
				result.add(pom);
				result.add(" ");
			} else {
				if (pom.equals("+") || pom.equals("-") || pom.equals("*")
						|| pom.equals("/") || pom.equals("^")
						|| lZn.contains(pom)) {
					while (!temp.IsEmpty() && !(temp.show().equals("("))
							&& howImportant(pom) <= howImportant(temp.show())) {
						result.add(temp.pop());
						result.add(" ");
					}
					temp.pusch(pom);

				} else {

					if (pom.equals("(")) {
						temp.pusch(pom);
					} else {
						if (pom.equals(")")) {
							if (temp.IsEmpty())
								throw new NawiasyEx();
							while (!temp.show().equals("(")) {
								result.add(temp.pop());
								result.add(" ");
								if (temp.IsEmpty())
									throw new NawiasyEx();
							}
							temp.pop();
						} else {

							try {
								throw new WyrażenieEx();
							} catch (WyrażenieEx e) {
								System.out.println(e.toString());

							}
						}
					}
				}
			}

		}

		while (!temp.IsEmpty()) {
			String s = temp.pop();
			if (s.equals("("))
				throw new NawiasyEx();
			else {
				result.add(s);
				result.add(" ");

			}
		}
		result.remove(result.size() - 1);
		return result;
	}
	/**
	 * Roznicuje waznosc operatow
	 * @param a nazwa operatora
	 * @returnzwraca wartosc int okreslajaca jak
	 * wazny jest dany operator
	 */
	private static int howImportant(String a) {
		int imp;
		if (a.equals("+") || a.equals("-"))
			imp = 1;
		else {
			if (a.equals("*") || a.equals("/"))
				imp = 2;
			else if (a.equals("^"))
				imp = 3;
			else
				imp = 4;
		}
		return imp;
	}
	/**
	 * Oblicza dzialanie okreslone operatorem dwuargumenowym
	 * @param a pierwszy argument operatora
	 * @param b drugi argument operatora
	 * @param dz nazwa operatora
	 * @return wynik dzialania
	 */
	private static double wykonajDziałanie(double a, double b, String dz) {
		double wynik;
		switch (dz.charAt(0)) {
		case ('+'):
			wynik = a + b;
			break;
		case ('-'):
			wynik = a - b;
			break;
		case ('*'):
			wynik = a * b;
			break;
		case ('^'):
			wynik = Math.pow(a, b);
			System.out.println(a + "^" + b + "=" + wynik);
			break;
		default:
			wynik = a / b;
			break;
		}
		return wynik;
	}
	/**
	 * Oblicza dzialanie okreslone operatorem jednoargumentowym
	 * @param a argument
	 * @param dz nazwa operatora
	 * @return wynik dzialania
	 */
	private static double funkcjaJednoArgumentowa(double a, String dz) {
		double wynik;
		switch (dz.charAt(0)) {
		case ('c'):
			if (dz.charAt(1) == 'o') {
				wynik = Math.cos(a);
			} else {
				wynik = 1 / Math.tan(a);
			}
			break;
		case ('s'):
			wynik = Math.sin(a);
			break;
		case ('t'):
			wynik = Math.tan(a);
			break;
		default:
			wynik = Math.log10(a);
			break;
		}
		return wynik;
	}
	/**
	 * Oblicza wynik podanego wyrazenie zapisanego 
	 * w odwrotnej notacji polskiej
	 * @param x wartosc zmiennej wsytepujacej w wyrazeniu
	 * @param tab tablica string zawierajaca skladowe wyrazenia 
	 * zapisane w odrotnej natacji polskiej
	 * @return wynik wyrazenia
	 * @throws OperatoryEx Wyjatek wystepuje gdy w wyrazeniu jest 
	 * nieprawidlowa ilosc operatorow 
	 */
	protected static double obliczWyrażenie(double x, ArrayList<String> tab)
			throws OperatoryEx {
		Iterator<String> it = tab.iterator();
		StackList<Double> rezultat = new StackList<Double>();
		while (it.hasNext()) {
			String pom = it.next();
			if (it.hasNext() && pom.equals(" ")) {
				pom = it.next();
			}

			if (pom.matches("(-*\\d+(\\.?\\d+)*)|-?x|-?e|-?π")) {
				if (pom.matches("-?x")) {
					if (pom.equals("x"))
						rezultat.pusch(x);
					else
						rezultat.pusch(-x);
				}

				else {
					if (pom.matches("-?e")) {
						if (pom.equals("e"))
							rezultat.pusch(Math.E);
						else
							rezultat.pusch(-Math.E);
					} else {
						if (pom.matches("-?π")) {
							if (pom.equals("π"))
								rezultat.pusch(Math.PI);
							else
								rezultat.pusch(-Math.PI);
						} else
							rezultat.pusch(Double.parseDouble(pom));
					}
				}
			} else {
				if (pom.equals("-") || pom.equals("+") || pom.equals("*")
						|| pom.equals("/") || pom.equals("^")) {
					if (rezultat.IsEmpty())
						throw new OperatoryEx();
					double b = rezultat.pop();
					if (rezultat.IsEmpty())
						throw new OperatoryEx();
					double a = rezultat.pop();
					rezultat.pusch(wykonajDziałanie(a, b, pom));
				} else {
					if (rezultat.IsEmpty())
						throw new OperatoryEx();
					rezultat.pusch(funkcjaJednoArgumentowa(rezultat.pop(), pom));
				}
			}
		}
		double wynik = rezultat.pop();
		if (!rezultat.IsEmpty())
			throw new OperatoryEx();
		return wynik;
	}
	/**
	 * Sprawdza czy w w wyrazenie nie ma literowek
	 * @param tab tablica stringow zawierajaca skladowe wyrazenia
	 * @return tablica stringow podana w paramentrze jesli 
	 * wyrazenie nie zawiera bledow lub wartosc null w przeciwnym wypadku
	 */
	protected static ArrayList<String> przesiewPierwszy(ArrayList<String> tab) {
		Iterator<String> it = tab.iterator();
		boolean blad = false;
		while (!blad && it.hasNext()) {
			String pom = it.next();
			blad = pom == null;
			if (!blad) {
				if (pom.matches("-\\d.*|\\d.*"))
					blad = !pom.matches("-?\\d+\\.\\d+|-?\\d+");
				else {
					if (pom.length() == 1)
						blad = !pom.matches("\\+|\\-|\\*|/|\\(|\\)|x|e|\\^|π");
				}
			}
		}

		if (blad)
			return null;
		else
			return tab;
	}
	/**
	 * Wylicza wartosc funkcji w konrektym punkcie
	 * @param x punkt dla, ktorego ma byc wyliczona wartosc funkcji
	 * @return wyliczona wartosc funkcji.
	 */
	public Double wartoscFunkcji(double x) {
		Double wynik;
		ArrayList<String> tab = przesiewPierwszy(getArrayString(funkcja));
		if (tab == null) {
			System.out.println("Znalazlem literówke");
			wynik = null;
		} else
			try {
				try {
					return obliczWyrażenie(x, transelt(tab));
				} catch (OperatoryEx e) {
					System.out.println(e.toString());
					return null;
				}
			} catch (NawiasyEx e) {
				System.out.println(e.toString());
				return null;
			}
		return wynik;
	}
}
